package main

import (
	"fmt"
	"strings"
)

/*
*
切片
Go语言中的切片是对数组的一种抽象。
GO语言中的数组的长度是不可改变的，在特定的场景下就不太适用了，Go中提供了一种灵活，且功能强悍的内置类型，91_切片（动态数组），
与数组相比切片的长度是固定不变的，可以最钾元素，在追加是可能使得切片容量增大。
需要说明的是，slice斌不是数组或者数组指针。他通过内部指针和相关属性引用数组片段，以实现边长方案。

1.91_切片：切片是数组的一个引用，因此切片是引用类型。但自身是结构体，值拷贝传递。
2.切片的长度可以改变，因此切片相当于一个可变的数组。
3.切片的遍历方式和数组一样，可以用len()求长度，表示可用元素数量，读写操作不能超过该限制。
4.cap可以求出slice最大扩张容量，不能超出数组限制。0<=lin(slice)<=len(array),其中array是slice 引用的数组
5.切片的定义： var 切片变量 [] 类型，，比如 var str []string、 var arr []int。
6.如果slice==nil,那么len 、cap 结果都等于0

切片的结构体由3部分构成，Pointer 是指向一个数组的指针，len 代表当前切片的长度，cap 是当前切片的容量。cap 总是大于等于 len 的。

	type Slice struct {
		array unsafe.Pointer
		len   int
		cap   int
	}

总结：Go语言中的切片类型是从数组类型基础上发展出来的新类型，当声明一个数组的时候不指定该数组的长度，则该类型为切片（动态数组）
切片有自己独立的内部结构字段（len,cap array pointer）,并于其应用的底层数组，共用存储空间。
*/
func main() {
	fmt.Println("================声明切片=================")
	//方法一：
	//可以声明一个未指定大小的数组来定义切片，切片声明是不需要说明长度（[]没有声明长度说明这是一个切片，而不是数组，
	//因为数组声明是必须指定长度的）
	//var identifier []type
	var mySlice []int
	if mySlice == nil {
		fmt.Println("切片是空的")
		fmt.Println(mySlice)
		fmt.Println(len(mySlice))
	}
	//上面这种形式的只声明不初始化，这时切片默认初始化为nil : len=0, cap=0  slice=[]
	//之所以为nil,是因为没有分配存储空间
	//切片在未初始化之前默认为nil,长度为0
	/*
		多一嘴：
		nil 切片被用在很多标准库和内置函数中，描述一个不存在的切片的时候，就需要用到nil切片，比如函数发生异常的时候，
		返回的切片就是nil切片。nil 切片的指针指向nil.
		空切片一用来表示一个空的集合。比如数据库查询，一条结果也没有查到，那么就可以返回一个空切片。
	*/

	//方法二：
	//如果你想声明一个拥有初始长度或者规定容量的切片（可以指定切片的长度和容量），可以使用make()函数来创建切片
	/*
		var mySlice1=make([]type,length,capacity)
			也可以简写为
		mySlice1:=make([]type,length,capacity)
		这里length是数组的长度，并且也是切片的初始长度。
		容量capacity 为可选参数（可选的意思是可以缺省，如果不指定capacity,则capacity 默认等于length）
	*/
	var mySlice1 = make([]int, 3)
	fmt.Println(mySlice1)
	/**
	向上面创切片，不初始化，那么切片被系统自动初始化为0，（而不是nil）
	之所有不是nil,是因为 make 函数为其分类了内存空间。
	*/
	/**
	有四种创建切片的方法：
		1.常规声明
		2.make() 函数创建
		3.赋值符 := 创建
		4.引用数组
	其中，只有 “常规声明” 却不初始化的切片被系统默认为 nil （没有内存空间）。
	用 make() 函数或 := 创建却不初始化的切片为空切片（拥有内存空间，只是没有元素），
	如果有元素的话会被系统默认初始化为 0 。这是因为 “常规声明” 不会为切片分配存储空间，而其他方法会分配。
	空切片和 nil 切片的区别在于，空切片指向的地址不是 nil，指向的是一个内存地址，但是它没有分配任何内存空间,
	即底层元素包含0个元素。最后需要说明的一点是。不管是使用 nil 切片还是空切片，
	对其调用内置函数 append，len 和 cap 的效果都是一样的。
	*/
	fmt.Println("===============切片初始化================")
	//1.在声明的时候直接初始化
	//s := []int{1, 2, 3}
	//fmt.Println(s)
	/**
	上面直接完成了切片的声明和初始化：
		[] 表示的是切片类型
		{1,2,3} 初始化的值依次是1,2,3
		其中cap=len=3,
	*/

	//2.使用数组初始化切片
	//初始化切片s是数组的arr的应用，
	//s:=arr[:]                    //用数组arr的所有值初始化切片
	//s:=arr[startIndex:endIndex]  //将arr中从下标startIndex到endIndex的元素创建一个新的切片
	//s:=arr[startIndex:]          //默认endIndex时候将表示一直到arr 的最后一个元素
	//s:=arr[:endIndex]			   //默认startIndex的时候表示从arr的第一个元素开始
	//s1:=s[startIndex:endIndex]   //通过切片s初始化切片s1

	//读写操作实际上操作的是底层数组，只需要注意索引号的差别。（本质切片是数组的一个引用）
	data := [...]int{1, 2, 3, 4, 5, 6, 7}
	s := data[2:4]
	s[0] += 100
	s[1] += 200
	fmt.Println(data) //[1 2 103 204 5 6 7]
	fmt.Println(s)    //[103 204]
	/**
	我们使用数组初始化切片的时候在我们对切片中的数据进行操作的时候实际上就是在操作数组中的数据
	可见：对切片内容的改变实际上改变的是他所引用的数组。
	切片就像一个傀儡，一个指针，一个虚构，对他的操作就是对原数组的操作，切片和他引用的数组是一体的。
	虽然我们看到的是一个切片，其实他还是底层的数组，它们两者是统一的，你就把切片当成一个原数组的一段就行了

	这时候会可能就有同学有疑问了，前面这么多创建切片的方式，并不都是通过引用数组得来的呀，大部分都是直接创建切片的呀？
	这是因为，我们直接创建slice 对象的时候，系统会自动分配底层数组

	切片的优点：切点长度可变的灵活性，：使用make动态创建slice ,避免了数组必须使用常量做长度的麻烦。

	*/

	//我们可以用指针直接访问底层数组，退化成普通数组的操作
	arr1 := []int{0, 1, 2, 3}
	p := &arr1[2] //*int 获取底层数组的元素指针
	*p += 100
	fmt.Println(arr1)
	fmt.Println("=============复杂类型的切片==============")

	//1. [][]T ,是指元素类型为[]T 的切片。 （二维数组）

	data1 := [][]int{
		[]int{1, 2, 3},
		[]int{4, 5, 6},
		[]int{7, 8, 9},
	}
	fmt.Println(data1)

	//2.结构体数组或者切片  (对象数组)
	d := [5]struct {
		x int
	}{} //结构体类型的切片未初始化。
	s2 := d[:]
	d[1].x = 100
	s2[2].x = 300
	fmt.Println(d)
	fmt.Printf("%p,%p\n", &d, &d[0])

	fmt.Println("=======len(),cap(),append(),copy()======")

	//可以使用len()  方法获取切片的长度
	//cap()方法可以计算切片容量，测量切片最长可以达到多少。

	numbers := make([]int, 3, 5)
	fmt.Printf("len=%d cap=%d slice=%v\n", len(numbers), cap(numbers), numbers)

	//append() 函数 用append 内置函数实现切片的追加，
	var a = []int{1, 2, 3}
	fmt.Printf("slice a : %v\n", a)
	a = append(a, 0) //在切片a的最后面添加一个元素0 把并将结果重新赋值给切片a
	fmt.Printf("slice a:%v\n", a)
	var b = []int{4, 5, 6}
	fmt.Printf("slice b : %v\n", b)
	c := append(a, b...) //将切片b中的数据全部添加到切片a的最后面，并将得到的结果赋值给 c
	fmt.Printf("slice c : %v\n", c)
	d1 := append(c, 7)
	fmt.Printf("slice d : %v\n", d1)
	e := append(d1, 8, 9, 10)
	fmt.Printf("slice e : %v\n", e)

	//append 函数的原理就是：向slice 尾部添加数据，返回新的slice 对象。
	old := make([]int, 0, 5)
	fmt.Printf("%p\n", &old)
	fmt.Println(old)

	new := append(old, 1)
	fmt.Printf("%p\n", &new)
	fmt.Println(new)

	//如果给切片append 的元素超过了原 slice.cap 的限制，就会重新给slice 分配底层数组，并进行扩容
	//如果不超过切片的容量就没事，操作的还是原数组，只是len 增加了。
	moreThanCap()
	/**
	从输出结果我们可以发现 append 后超过原cap的切片被重新分配了底层数组，（也就是说，s的底层数组不再是data，
	那么修改s的值不会在影响原来切片对应的数组，他们之间不会在有关联），并把原数组拷贝到新数组中，这是因为超出了原切片的容量。

	所以在大批量添加数据的时候，建议一次性分配足够大的空间，以减少内存分配和数据复制的开销。或 初始化足够长的len 属性该索引号进行操作。
	及时释放不在使用的slice对象，避免持有过期数组，造成GC（垃圾收集） 无法回收
	*/

	//copy 函数（切片的深拷贝）   语法：copy(目的切片,源切片)
	/*
		切片的拷贝分为2种，一种是浅拷贝，一种是深拷贝。
		浅拷贝：源切片和目的切片共享同一底层数组空间，源切片修改，目的切片同样被修改。（赋值符实现）
		深拷贝：源切片和目的切片各自都有彼此独立的底层数组空间，各自的修改，彼此不受影响。（使用内置函数copy()函数实现）
	*/
	shallowCopy()
	deepCopy()
	//copy 函数的原理：copy 函数在两个slice 间复制数据，复制长度以len 小的为准，两个slice可指向同一底层数组，允许元素区间重叠。
	//k1 := []int{1, 2, 3}
	k2 := []int{4, 5, 6}
	k3 := []int{1}
	copy(k2, k3)
	fmt.Println(k2)
	//如果想 增加切片的容量，我们可以 创建一个新的更大的切片 并 把原切片的内容都拷贝过来。

	//copy 函数只会拷贝目标切片的长度个元素，并且 copy 后两个切片是互相没有影响的。

	//应及时将所需数据 copy 到较小的 slice，以便释放超大号底层数组内存。

	fmt.Println("================切片遍历=================")
	traverseSlice()

	fmt.Println("==============字符串和切片===============")
	//1.string底层就是一个byte的数组，因此也可以进行切片的操作

	//对字符串进行切片操作 中文字符串需要使用[]rune(str)
	stringSlice()

	//2.string本身是不可改变的，因此要改变string中的字符，需要如下操作。
	changeString()

	//数组或者切片转化为字符串
	array_or_slice := []int{1, 3, 4}
	str := strings.Replace(strings.Trim(fmt.Sprint(array_or_slice), "[]"), " ", ",", -1)
	fmt.Println(str)

	fmt.Println("========对切片[x:y:z]两个冒号的理解========")
	//常规切片截取（一个冒号）
	//可以通过设置下限及上限来设置截取切片[lower_bound:upper_bound],截取的内容是从lower_bound 到 upper_bound-1

	//两个冒号的截取：
	slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	d11 := slice[6:8]
	fmt.Println(d1, len(d11), cap(d11)) //[1 2 3 0 4 5 6 7] 2 4
	d2 := slice[:6:8]
	fmt.Println(d2, len(d2), cap(d2)) //[0 1 2 3 4 5] 6 8
	/**
	两个冒号的截取：slice[:6:8] slice内容为 从0到第五位，长度len为6，最大扩充项cap设置为8（0到7）
	所以说，两个冒号相对于一个冒号多的那个冒号是第二个冒号，这个冒号后的数字用于控制最大容量。
	或者说，常规切片截取相对于两个冒号的截取少了第二个冒号，省略了，默认第二个冒号的位置到尾部
	总结：a[x:y:z] 切片内容是： [x:y] 切片长度，[x:z] 切片容量。长度计算：y-x，容量计算：z-x。
	*/
}

// append 超过切片的cap
func moreThanCap() {
	data := [...]int{0, 1, 2, 3, 4, 10: 0} //数组
	s := data[:2:3]
	fmt.Println(s)
	fmt.Println(len(s), cap(s))
	fmt.Println(&s[0], &data[0]) // 比对底层数组起始指针。(没超过cap容量还是一样的)

	s = append(s, 100, 200, 300) // 一次 append 三个值，超出 s.cap 限制。

	fmt.Println(s, data)         // 重新分配底层数组，与原数组无关。
	fmt.Println(&s[0], &data[0]) // 比对底层数组起始指针。(超过cap容量已经不一样了)

}

// 浅拷贝
func shallowCopy() {
	slice1 := make([]int, 5, 5)
	slice2 := slice1
	slice1[1] = 1
	fmt.Println(slice1) //[0 1 0 0 0]
	fmt.Println(slice2) //[0 1 0 0 0]

}

// 深拷贝 copy(目的切片,源切片)
func deepCopy() {
	slice1 := make([]int, 5, 5)
	slice1[0] = 9
	fmt.Println(slice1)
	slice2 := make([]int, 4, 4)
	slice3 := make([]int, 5, 5)
	fmt.Println(slice2)
	fmt.Println(slice3)
	//拷贝
	fmt.Println(copy(slice2, slice1)) //4
	fmt.Println(copy(slice3, slice1)) //5
	//独立修改
	slice2[1] = 2
	slice3[1] = 3
	fmt.Println(slice1) //[9 0 0 0 0 0]
	fmt.Println(slice2) //[9 2 0 0]
	fmt.Println(slice3) //[9 3 0 0 0]
}

// 遍历切片
func traverseSlice() {
	data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	slice := data[:]
	for index, value := range slice {
		fmt.Printf("inde : %v , value : %v\n", index, value)
	}
}

// 对字符串进行切片操作
func stringSlice() {
	str := "hello world"
	s1 := str[0:5]
	fmt.Println(s1) //hello

	s2 := str[6:]
	fmt.Println(s2) //world
}

// 改变string中的字符：
func changeString() {

	str := "Hello world"
	s := []byte(str) //将字符串类型转换成一个切片，中文字符需要用[]rune(str)
	fmt.Println(s)

	s[6] = 'G'
	s = s[:8]
	s = append(s, '!')
	fmt.Println(s)

	str = string(s) //将切片转换成字符串
	fmt.Println(str)

}
